package com.vains.config.security;

import com.vains.io.JsonUtils;
import com.vains.service.impl.UserDetailsServiceImpl;
import com.vains.system.model.ErrorTokenModel;
import com.vains.system.model.Result;
import com.vains.system.util.ResultUtils;
import com.vains.utils.SpringContextUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final UserDetailsServiceImpl userDetailsService;

    private final AuthenticationManager authenticationManager;

    private final AuthorizationServerTokenServices tokenServices;

    private final ClientDetailsService jdbcClientDetailsService;

    private final AuthorizationCodeServices authorizationCodeServices;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                //若无，refresh_token会有UserDetailsService is required错误
                .userDetailsService(userDetailsService)
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(tokenServices)
                .exceptionTranslator(this::exceptionHandler)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);;
    }

    /**
     * 请求 /oauth/token 发生异常时执行
     * @param e 具体的异常
     * @return 用response写回数据了, 这里不用返回
     */
    private ResponseEntity<OAuth2Exception> exceptionHandler(Exception e) {
        HttpServletResponse response = SpringContextUtils.getCurrentResponse();
        response.setContentType("application/json; charset=utf-8");
        try {
            String text;
            if (e instanceof InvalidGrantException) {
                // 获取错误信息和类型
                InvalidGrantException grantException = (InvalidGrantException) e;
                ErrorTokenModel errorToken = new ErrorTokenModel(grantException.getMessage(), null, grantException.getOAuth2ErrorCode());
                Result<ErrorTokenModel> error = ResultUtils.error(grantException.getMessage(), errorToken);
                text = JsonUtils.objectCovertToJson(error);
            } else {
                text = JsonUtils.objectCovertToJson(ResultUtils.error(e.getMessage()));
            }
            response.getWriter().println(text);
        } catch (IOException ex) {
            log.error("认证失败时提醒失败:", ex);
        }
        return null;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService);
    }

}